<!DOCTYPE html>
<title>Credential Manager: Call get() across browsing contexts.</title>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<body>
<script type="module">
import {ACCEPTABLE_CREDENTIAL, ACCEPTABLE_CREDENTIAL_ID, deepCopy, encloseInScriptTag, GET_CREDENTIAL, GET_CREDENTIAL_OPTIONS, MAKE_CREDENTIAL_OPTIONS, PUBLIC_KEY_USER} from './resources/test-inputs.js';
import {TestAuthenticatorManager} from './resources/virtual-navigator-credentials.js';

if (document.location.host != "subdomain.example.test:8443") {
  document.location = "https://subdomain.example.test:8443/credentialmanager/credentialscontainer-get-from-nested-frame.html";
  promise_test(_ => new Promise(_ => {}), "Stall tests on the wrong host.");
}

const manager = new TestAuthenticatorManager;

promise_test(async _ => {
  let authenticators = await manager.authenticators();
  assert_equals(authenticators.length, 0);
  let testAuthenticator = await manager.createAuthenticator();
  assert_true(await testAuthenticator.generateAndRegisterKey(ACCEPTABLE_CREDENTIAL_ID, "subdomain.example.test"));
}, "Set up the testing environment.");

promise_test(t => {
  let PROBE_CREDENTIALS = "window.parent.postMessage(String(navigator.credentials), '*');";

  let frame = document.createElement("iframe");
  frame.src = "data:text/html," + encloseInScriptTag(PROBE_CREDENTIALS);
  window.setTimeout(_ => document.body.append(frame));

  let eventWatcher = new EventWatcher(t, window, "message");
  return eventWatcher.wait_for("message").then(message => {
    assert_equals(message.data, "undefined");
  });
}, "navigator.credentials should be undefined in documents generated from `data:` URLs.");

promise_test(t => {
  let frame = document.createElement("iframe");
  frame.src = "resources/nested-document.html";
  window.setTimeout(_ => document.body.append(frame));

  let loadWatcher = new EventWatcher(t, frame, "load");
  loadWatcher.wait_for("load").then(_ =>
      frame.contentWindow.location = "javascript:" + GET_CREDENTIAL);

  let messageWatcher = new EventWatcher(t, window, "message");
  return messageWatcher.wait_for("message").then(message => {
    assert_equals(message.data, "[object PublicKeyCredential]");
  });
}, "navigator.credentials.get({publicKey}) in a javascript url should should succeed.");

promise_test(t => {
  let frame = document.createElement("iframe");
  frame.srcdoc = '';
  window.setTimeout(_ => document.body.append(frame));

  let loadWatcher = new EventWatcher(t, frame, "load");
  loadWatcher.wait_for("load").then(_ => {
    frame.contentWindow.eval(GET_CREDENTIAL);
  });

  let eventWatcher = new EventWatcher(t, window, "message");
  return eventWatcher.wait_for("message").then(message => {
    assert_equals(message.data, "[object PublicKeyCredential]");
  });
}, "navigator.credentials.get({publicKey}) in srcdoc should succeed.");

promise_test(t => {
  let frame = document.createElement("iframe");
  frame.src = "about:blank";
  window.setTimeout(_ => document.body.append(frame));

  let loadWatcher = new EventWatcher(t, frame, "load");
  loadWatcher.wait_for("load").then(_ => {
    frame.contentDocument.write(encloseInScriptTag(GET_CREDENTIAL));
  });

  let eventWatcher = new EventWatcher(t, window, "message");
  return eventWatcher.wait_for("message").then(message => {
    assert_equals(message.data, "[object PublicKeyCredential]");
  });
}, "navigator.credentials.get({publicKey}) in about:blank embedded in a secure context should succeed.");

promise_test(t => {
  let frame = document.createElement("iframe");
  frame.src = "about:blank";
  window.setTimeout(_ => document.body.append(frame));

  let loadWatcher = new EventWatcher(t, frame, "load");
  loadWatcher.wait_for("load").then(_ => {
    frame.contentWindow.eval(GET_CREDENTIAL);
  });

  let eventWatcher = new EventWatcher(t, window, "message");
  return eventWatcher.wait_for("message").then(message => {
    assert_equals(message.data, "[object PublicKeyCredential]");
  });
}, "navigator.credentials.get({publicKey}) in an about:blank page embedded in a secure context should pass rpID checks.");

promise_test(async t => {
  let testAuthenticator = await manager.createAuthenticator();
  t.add_cleanup(async () => {
    let id = await testAuthenticator.id();
    return manager.removeAuthenticator(id);
  });
  assert_true(await testAuthenticator.generateAndRegisterKey(ACCEPTABLE_CREDENTIAL_ID, "subdomain.example.test"));
  let keys = await testAuthenticator.registeredKeys();
  assert_equals(keys.length, 1);
  var customGetAssertionOptions = deepCopy(GET_CREDENTIAL_OPTIONS);
  var someOtherCredential = deepCopy(ACCEPTABLE_CREDENTIAL);
  someOtherCredential.id = new TextEncoder().encode("someOtherCredential");
  customGetAssertionOptions.allowCredentials = [someOtherCredential];

  return promise_rejects_dom(t, "NotAllowedError",
    navigator.credentials.get({ publicKey : customGetAssertionOptions}));
}, "navigator.credentials.get() for unregistered device returns NotAllowedError");

promise_test(async t => {
  // Injecting resident credentials through the mojo virtual authenticator
  // interface (i.e |generateAndRegisterKey|) is not supported. Use
  // |navigator.credentials.create({})| instead.
  var customMakeCredOptions = deepCopy(MAKE_CREDENTIAL_OPTIONS);
  customMakeCredOptions.authenticatorSelection.requireResidentKey = true;
  var residentCredential =
    await navigator.credentials.create({ publicKey : customMakeCredOptions });

  var customGetAssertionOptions = deepCopy(GET_CREDENTIAL_OPTIONS);
  delete customGetAssertionOptions.allowCredentials;
  customGetAssertionOptions.user = PUBLIC_KEY_USER;
  return navigator.credentials.get({ publicKey : customGetAssertionOptions })
    .then(credential => assert_equals(credential.id, residentCredential.id));
}, "navigator.credentials.get() with empty allowCredentials returns a resident credential");

promise_test(t => {
  return manager.clearAuthenticators();
}, "Clean up testing environment.");

</script>
